ext.define('extension.filters', function() {

var utils = extension.utils;
var messages = extension.messages;
var UNBLOCKED = 1;
var BLOCKED = 2;
var ALLOWED = 4;
var DENIED = 8;
var GLOBAL = 16;
var frame_blocked = 0;
var flag_override = 0;
var meta_data = {};
var target_filters = {};
var source_filters = {};

function FilterMatcher(url) {
    var hostname = utils.origin(url) || window.location.hostname;
    var targetMatcher = function(rx) {
        return rx.test(url);
    };
    var sourceMatcher = function(filter) {
        return filter.origin.test(hostname) && filter.patterns.some(targetMatcher);
    };
    this.matchTarget = targetMatcher;
    this.matchSource = sourceMatcher;
}

function blockMutation(records) {
    if (records[0].target.style.display !== 'none') {
        var metadata = getMetadata(records[0].target);
        allowMutation(metadata);
        blockElement(metadata);
    }
}

function preventMutation(metadata) {
    metadata.observer.observe(metadata.element,
        { 'attributes': true, 'attributeFilter': ['style'] });
}

function allowMutation(metadata) {
    metadata.observer.disconnect();
}

function createMetadata(element) {
    var metadata = {
        id: Math.random().toString(16),
        flags: 0,
        element: element,
        observer: new window.MutationObserver(blockMutation)
    };
    return meta_data[metadata.id] = metadata;
}

function getMetadata(element) {
    return searchMetadata('element', element) || createMetadata(element);
}

function removeMetadata(element) {
    var metadata = getMetadata(element);
    allowMutation(metadata);
    deleteMetadata(metadata);
    return metadata;
}

function deleteMetadata(metadataArg) {
    var metadata = meta_data;
    for (var k in metadata) {
        if (metadata[k].id == metadataArg.id)
            return delete metadata[k];
    }
}

function searchMetadata(argA, argB) {
    var metadata = meta_data;
    if (arguments.length == 2) {
        for (var k in metadata) {
            if (metadata[k][argA] === argB)
                return metadata[k];
        }
        return;
    }
    for (var k in metadata) {
        if (metadata[k] === argA)
            return metadata[k];
    }
}

function patternMatcher(str) {
    return function(rx) {
        return rx.source != str;
    }
}

function matchTargetFilter(type, matcher) {
    var filter = target_filters[type];
    return filter ? filter.some(matcher.matchTarget) : false;
}

function matchSourceFilter(type, matcher) {
    var filter = source_filters[type];
    return filter ? filter.some(matcher.matchSource) : false;
}

function loadSourceFilters(type, data) {
    var filters = source_filters[type] = [];
    for (var i = 0; i < data.length; i++) {
        filters.push({
            origin: utils.regexp(data[i].root),
            patterns: data[i].patterns.map(utils.regexp)
        });
    }
}

function loadTargetFilters(type, data) {
    target_filters[type] = data.map(utils.regexp);
}

function updateTargetFilters(type, str, enabled) {
    var targets = target_filters[type] || [];
    var pattern = utils.regexp(str);
    var matcher = patternMatcher(pattern.source);
    if (!enabled)
        targets = targets.filter(matcher);
    else if (targets.every(matcher))
        targets.push(pattern);
    target_filters[type] = targets;
}

function updateTarget(data) {
    updateTargetFilters(data.type, data.pattern, data.enabled);
}

function getInfo(metadata) {
    var element = metadata.element;
    var url = utils.getPluginSRC(element);
    var flags = flag_override || metadata.flags;
    var view = window.getComputedStyle(element, null);
    var info = {
        'id': metadata.id,
        'src': url,
        'status': '',
        'type': element.type,
        'vars': utils.getPluginParam(element, 'flashvars'),
        'access': utils.getPluginParam(element, 'allowscriptaccess') || 'sameDomain',
        'height': view.height,
        'width': view.width
    };
    if (flags & DENIED)
        info.status = 'blacklisted';
    if (flags & ALLOWED)
        info.status = 'whitelisted';
    if (flags & GLOBAL)
        info.status = 'g' + info.status;
    if (flags & BLOCKED)
        info.status = 'blocked';
    if (flags & UNBLOCKED)
        info.status = 'unblocked';
    return info;
}

function getElementFlags(element) {
    var url = utils.getPluginSRC(element);
    var matcher = new FilterMatcher(url);
    if (matchTargetFilter('black', matcher))
        return DENIED;
    if (matchTargetFilter('white', matcher))
        return ALLOWED;
    if (matchSourceFilter('black', matcher))
        return DENIED | GLOBAL;
    if (matchSourceFilter('white', matcher))
        return ALLOWED | GLOBAL;
    return frame_blocked;
}

function onInsert(element) {
    var metadata = getMetadata(element);
    var flags = metadata.flags = flag_override || getElementFlags(element);
    if (flags & DENIED) {
        blockElement(metadata);
        messages.send('cancel', metadata);
        return;
    }
    if (flags & ALLOWED) {
        unblockElement(metadata);
        messages.send('allow', metadata);
        return;
    }
    if (flags & BLOCKED) {
        blockElement(metadata);
        messages.send('restrict', metadata);
        return;
    }
    if (flags & UNBLOCKED) {
        unblockElement(metadata);
        messages.send('allow', metadata);
        return;
    }
}

function onRemove(element) {
    var metadata = removeMetadata(element);
    messages.send('cancel', metadata);
}

function onBlock(metadata) {
    blockElement(searchMetadata('id', metadata.id));
}

function onUnblock(metadata) {
    unblockElement(searchMetadata('id', metadata.id));
}

function unblockElement(metadata) {
    allowMutation(metadata);
    metadata.element.style.setProperty('display', 'block', 'important');
}

function blockElement(metadata) {
    metadata.element.style.setProperty('display', 'none', 'important');
    preventMutation(metadata);
}

function destroyFilters() {
    target_filters = {};
    source_filters = {};
}

function updateFilters(data) {
    if (Array.isArray(data))
        data.forEach(updateTarget);
    else
        updateTarget(data);
}

function getElementInfo(element) {
    var data = [];
    if (element)
        data.push(getInfo(getMetadata(element)));
    else {
        for (var k in meta_data)
            data.push(getInfo(meta_data[k]));
    }
    return data;
}

function blockContent(block) {
    frame_blocked = block ? BLOCKED : UNBLOCKED;
}

function denyContent() {
    flag_override = DENIED;
    destroyFilters();
}

function allowContent() {
    flag_override = ALLOWED;
    destroyFilters();
}

function captureContent(config) {
    flag_override = 0;
    blockContent(config['blocked']);
    var filterset = config['filterset'];
    loadTargetFilters('white', filterset[0]);
    loadTargetFilters('black', filterset[1]);
    loadSourceFilters('white', filterset[2]);
    loadSourceFilters('black', filterset[3]);
}

function searchMetadataById(id) {
    return searchMetadata('id', id);
}

return {
    bind: function() {
        messages.listen({
            'insert': onInsert,
            'remove': onRemove,
            'block': onBlock,
            'unblock': onUnblock
        });
    },
    search: searchMetadataById,
    capture: captureContent,
    allow: allowContent,
    deny: denyContent,
    block: blockContent,
    info: getElementInfo,
    update: updateFilters
};

});
